package Controller;

import Model.Car;
import Model.ChatClientModel.ChatClient;
import Model.InterGrid;
import Model.TrafficController;
import View.DisplayPanel;
import View.IntersectionOptions;
import View.MainMenu;
import View.SimulationFrame;

import javax.swing.*;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author Matthew
 *         Date: Oct 25, 2010
 *         Time: 2:13:41 PM
 */
public class SimulationHandler {
    //Declare Models
    private InterGrid interGrid;
    private TrafficController tc;

    //Declare Views
    private MainMenu mainMenu;
    private IntersectionOptions settingsGui;
    private SimulationFrame simulationGui;
    private DisplayPanel display;
    private ChatClient chatClient;
    private SimulationCommandHandler simCommand;

    //Handler Variables
    private Random rand;
    private ArrayList<Car> cars;
    private ArrayList saveArray;
    private ArrayList openArray;
    private ArrayList carSaveArray;
    private char grid[][];
    private Timer timer;
    private boolean simulationRunning;
    private FileHandler fileHandler;

    private int numHorLanes;
    private int numVertLanes;
    private double numhorProb;
    private double numvertProb;
    private JTextField horLane, vertLane, horProb, vertProb;
    private int runCount;
    private boolean simulationRunningCommand;
    private int runTimes;

    /**
     * Constructs the simulation handler
     */
    public SimulationHandler() {
        //Create models
        interGrid = new InterGrid();
        tc = new TrafficController();

        //Create views
        display = new DisplayPanel(interGrid);
        simulationGui = new SimulationFrame(display);
        settingsGui = new IntersectionOptions(this);
        chatClient = new ChatClient(this);
        mainMenu = new MainMenu(this);
        simCommand = new SimulationCommandHandler(interGrid, this);

        //Initiate variables
        rand = new Random();
        timer = new Timer();
        simulationRunning = false;
        simulationRunningCommand = false;
        fileHandler = new FileHandler();
        runTimes = 0;

        //Construct Grid
        interGrid.initiateGrid();

        //Initiate timer thread
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                if (simulationRunning == true) {
                    runSimulation();
                    display.repaint();
                }
                if (simulationRunningCommand == true) {
                    if (runCount < runTimes) {
                        runSimulation();
                        printGridToConsole();
                        runCount++;
                    } else {
                        simulationRunningCommand = false;
                        simCommand.showMenu();

                    }

                }
            }
        };
        timer.schedule(task, 10, 500);

    }

    /**
     * Switches running simulation boolean flag
     */
    public void runPauseSimulation() {
        this.simulationRunning = !simulationRunning;
        System.out.println("Simulation = " + simulationRunning);
    }

    /**
     * Runs simulation once
     */
    public void runSimulation() {
        double prob;

        interGrid.eraseGrid();
        interGrid.createLaneBoundaries();
        interGrid.setElement(interGrid.getHorRowLight(), interGrid.getHorColLight(), tc.getHorState());
        interGrid.setElement(interGrid.getVertRowLight(), interGrid.getVertColLight(), tc.getVertState());
        cars = interGrid.getCars();

        if (cars.size() > 0) {
            for (int i = 0; i < cars.size(); i++) {
                Car car = cars.get(i);
                switch (car.getDirection()) {
                    case 'N':
                        //If car is past the street light
                        if (car.getRowLoc() <= interGrid.getVertRowLight()) {
                            if (car.getNextRowLoc() < 0) {

                            } else {
                                car.move();
                                interGrid.setElement(car.getRowLoc(), car.getColLoc(), car.getSymbol());
                            }
                        } else {
                            //If car is before street light
                            if (tc.getVertState() == 'R') {
                                //If Light is red
                                if (car.getNextRowLoc() != interGrid.getVertRowLight()) {
                                    //If space in front is free
                                    if (interGrid.getElement(car.getNextRowLoc(), car.getNextColLoc()) == ' ') {
                                        car.move();
                                    } else {
                                        //If space next to car in front is free
                                        if (interGrid.getElement(car.getNextRowLoc(), (car.getNextColLoc() + 1)) == ' ') {
                                            car.setRowLoc(car.getNextRowLoc());
                                            car.setColLoc(car.getNextColLoc() + 1);
                                            car.setNextRowLoc(car.getRowLoc() - 1);
                                            car.setNextColLoc(car.getColLoc());
                                        }
                                    }
                                }

                            } else {
                                car.move();
                            }
                            interGrid.setElement(car.getRowLoc(), car.getColLoc(), car.getSymbol());

                        }
                        break;
                    case 'E':
                        //If car is past the street light
                        if (car.getColLoc() >= interGrid.getHorColLight()) {
                            if (car.getNextColLoc() > (interGrid.getNumHorSquares() - 1)) {

                            } else {
                                car.move();
                                interGrid.setElement(car.getRowLoc(), car.getColLoc(), car.getSymbol());
                            }
                        } else {
                            //If car is before street light
                            if (tc.getHorState() == 'R') {
                                //If Light is red
                                if (car.getNextColLoc() != interGrid.getHorColLight()) {
                                    //If space in front is free
                                    if (interGrid.getElement(car.getNextRowLoc(), car.getNextColLoc()) == ' ') {
                                        car.move();
                                    } else {
                                        //If space next to car in front is free
                                        if (interGrid.getElement(car.getNextRowLoc() + 1, car.getNextColLoc()) == ' ') {
                                            car.setRowLoc(car.getNextRowLoc() + 1);
                                            car.setColLoc(car.getNextColLoc());
                                            car.setNextRowLoc(car.getRowLoc());
                                            car.setNextColLoc(car.getColLoc() + 1);
                                        }
                                    }
                                }
                            } else {
                                car.move();
                            }
                            interGrid.setElement(car.getRowLoc(), car.getColLoc(), car.getSymbol());
                        }
                        break;
                }
            }
        }

        prob = rand.nextDouble();
        //Check if car is to be created on the horizontal street.
        int horStart = rand.nextInt(interGrid.getNumVertLanes());
        if (prob < interGrid.getHorProbability() && interGrid.getElement(interGrid.getHorRowStart() + horStart, interGrid.getHorColStart()) == ' ') {
            int r = interGrid.getHorRowStart() + horStart;
            int c = interGrid.getHorColStart();
            char dir = 'E';
            switch (rand.nextInt(6)) {
                case 0:
                    cars.add(new Car(r, c, dir, 'A'));
                    interGrid.setElement(r, c, 'A');
                    break;
                case 1:
                    cars.add(new Car(r, c, dir, 'C'));
                    interGrid.setElement(r, c, 'C');
                    break;
                case 2:
                    cars.add(new Car(r, c, dir, 'E'));
                    interGrid.setElement(r, c, 'E');
                    break;
                case 3:
                    cars.add(new Car(r, c, dir, 'H'));
                    interGrid.setElement(r, c, 'H');
                    break;
                case 4:
                    cars.add(new Car(r, c, dir, 'J'));
                    interGrid.setElement(r, c, 'J');
                    break;
                case 5:
                    cars.add(new Car(r, c, dir, 'L'));
                    interGrid.setElement(r, c, 'L');
                    break;
            }
        }
        //Check if car is to be created on the vertical street.
        int vertStart = rand.nextInt(interGrid.getNumVertLanes());

        if (prob < interGrid.getVertProbability() && interGrid.getElement(interGrid.getVertRowStart(), (interGrid.getVertColStart() + vertStart)) == ' ') {
            int r = interGrid.getVertRowStart();
            int c = interGrid.getVertColStart() + vertStart;
            char dir = 'N';
            switch (rand.nextInt(6)) {
                case 0:
                    cars.add(new Car(r, c, dir, 'B'));
                    interGrid.setElement(r, c, 'B');
                    break;
                case 1:
                    cars.add(new Car(r, c, dir, 'D'));
                    interGrid.setElement(r, c, 'D');
                    break;
                case 2:
                    cars.add(new Car(r, c, dir, 'F'));
                    interGrid.setElement(r, c, 'F');
                    break;
                case 3:
                    cars.add(new Car(r, c, dir, 'I'));
                    interGrid.setElement(r, c, 'I');
                    break;
                case 4:
                    cars.add(new Car(r, c, dir, 'K'));
                    interGrid.setElement(r, c, 'K');
                    break;
                case 5:
                    cars.add(new Car(r, c, dir, 'M'));
                    interGrid.setElement(r, c, 'M');
                    break;
            }
        }

        tc.incrementCycle();

    }

    /**
     * Prints grid contents to console
     */
    public void printGridToConsole() {
        grid = interGrid.getGrid();
        for (int i = 0; i < grid.length; i++) {
            char[] row = grid[i];
            for (int j = 0; j < row.length; j++) {
                char c = row[j];
                System.out.print(c);
            }
            System.out.println("");

        }
        System.out.println("------------------------End Simulation---------------------------");
    }

    /**
     * Checks user input from JTextFields
     */
    public void getUserInput() {
        boolean inputCheck;
        horLane = settingsGui.getHorLanesText();
        vertLane = settingsGui.getVertLanesText();
        horProb = settingsGui.getHorProbText();
        vertProb = settingsGui.getVertProbText();

        try {
            this.numHorLanes = Integer.parseInt(horLane.getText());
            inputCheck = true;
        } catch (NumberFormatException f) {
            inputCheck = false;
        }
        checkInputs(inputCheck, horLane, "Horizontal Lanes");
        System.out.println("HorLanesInput is " + inputCheck);

        try {
            this.numVertLanes = Integer.parseInt(vertLane.getText());
            inputCheck = true;
        } catch (NumberFormatException f) {
            inputCheck = false;
        }
        checkInputs(inputCheck, vertLane, "Vertical Lanes");
        System.out.println("VertLanesInput is " + inputCheck);

        try {
            this.numhorProb = Double.parseDouble(horProb.getText());
            inputCheck = true;
        } catch (NumberFormatException f) {
            inputCheck = false;
        }
        checkInputs(inputCheck, horProb, "Horizontal Probability");
        System.out.println("Horprobinput is " + inputCheck);

        try {
            this.numvertProb = Double.parseDouble(vertProb.getText());
            inputCheck = true;
        } catch (NumberFormatException f) {
            inputCheck = false;
        }
        checkInputs(inputCheck, vertProb, "Vertical Probability");
        System.out.println("Vertprobinput is " + inputCheck);
    }

    /**
     * Checks user input and saves to grid
     */
    public void checkAndSaveUserInput() {
        boolean inputCheck;
        inputCheck = interGrid.setVertProbability(numvertProb);
        if (inputCheck == false) {
            JOptionPane.showMessageDialog(null, "Vertical probability out of range");
        }
        inputCheck = interGrid.setHorProbability(numhorProb);
        if (inputCheck == false) {
            JOptionPane.showMessageDialog(null, "Horizontal probability out of range");
        }
        inputCheck = interGrid.setNumVertLanes(numVertLanes);
        if (inputCheck == false) {
            JOptionPane.showMessageDialog(null, "Vertical lanes out of range");
        }
        inputCheck = interGrid.setNumHorLanes(numHorLanes);
        if (inputCheck == false) {
            JOptionPane.showMessageDialog(null, "Horizontal lanes out of range");
        }
        interGrid.initiateGrid();
        interGrid.createLaneBoundaries();
    }

    /**
     * Repaints dispay panel
     */
    public void updateDisplay() {
        display.repaint();
    }

    /**
     * Checks inputs for validity
     *
     * @param inputChk = Boolean
     * @param text     = JTextField
     * @param message  = String
     */
    public void checkInputs(Boolean inputChk, JTextField text, String message) {
        if (inputChk == false) {
            JOptionPane.showMessageDialog(null, "Invalid Input for " + message + ", Please Try Again");
            text.setText("");
        }
    }

    /**
     * Sets chat client window to visible
     */
    public void showChatClient() {
        chatClient.setVisible(true);
    }

    /**
     * Sets intersection window to visible
     */
    public void showIntersectionSettingsWindow() {
        settingsGui.setVisible(true);
    }

    /**
     * Sets the simulation window to visible
     */
    public void showSimulaiton() {
        simulationGui.setVisible(true);
    }

    /**
     * Prints grid contents to console
     */
    public void setRunSimulationBttnLable() {
        JButton button = mainMenu.getRunSimulationBttn();
        if (simulationRunning == false) {
            button.setText("Run Simulation");
        } else {
            button.setText("Pause Simulation");
        }
    }

    /**
     * Exits the program after confirmation from user
     */
    public void exitProgram() {
        int optionReturn;
        optionReturn = JOptionPane.showConfirmDialog(null, "Are you sure you want to quit?");
        switch (optionReturn) {
            case 0:
                System.exit(0);
                break;
        }
    }

    /**
     * Closes the set intersection options window
     */
    public void closeIntersectionOptionsWindow() {
        settingsGui.setVisible(false);
    }

    /**
     * Gets all data required to save a simulation state
     */
    public void getSaveData() {
        //Get Grid Details
        int saveHorLanes = interGrid.getNumHorLanes();
        int saveVertLanes = interGrid.getNumVertLanes();
        double saveVertProb = interGrid.getVertProbability();
        double saveHorProb = interGrid.getHorProbability();
        carSaveArray = new ArrayList();
        saveArray = new ArrayList();

        saveArray.add(saveHorLanes);
        saveArray.add(saveVertLanes);
        saveArray.add(saveVertProb);
        saveArray.add(saveHorProb);
        saveArray.add(interGrid.getGrid());
        //Get traffic light state
        saveArray.add(tc.getCycleNumber());
        saveArray.add(tc.getWaitNumber());
        saveArray.add(tc.getHorState());
        saveArray.add(tc.getVertState());
        saveArray.add(tc.getPrevGreenState());
        //Get car details
        cars = interGrid.getCars();
        for (int i = 0; i < cars.size(); i++) {
            Car car = cars.get(i);
            ArrayList carSave = new ArrayList();
            int carRow = car.getRowLoc();
            int carCol = car.getColLoc();
            char carDir = car.getDirection();
            char carChar = car.getSymbol();
            carSave.add(carRow);
            carSave.add(carCol);
            carSave.add(carDir);
            carSave.add(carChar);
            carSaveArray.add(carSave);
        }
        saveArray.add(carSaveArray);
    }

    /**
     * Saves simulation data to file name specified by user
     */
    public void saveData() {
        String fileName = JOptionPane.showInputDialog(null, "Save file as:");
        try {
            fileHandler.save(saveArray, fileName);
            JOptionPane.showMessageDialog(null, "Simulation was successfully saved!");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * Opens simulation data from file name specified by user
     */
    public void openData() {
        String fileName = JOptionPane.showInputDialog(null, "Load file name:");
        try {
            openArray = fileHandler.open(fileName);
            loadSavedData();
        } catch (FileNotFoundException e) {
            JOptionPane.showMessageDialog(null, "File was not found. Please try again");
        }
    }

    /**
     * Loads saved data into simulation models
     */
    public void loadSavedData() {
        //Load grid settings
        interGrid.setNumHorLanes((Integer) openArray.get(0));
        interGrid.setNumVertLanes((Integer) openArray.get(1));
        interGrid.setVertProbability((Double) openArray.get(2));
        interGrid.setHorProbability((Double) openArray.get(3));
        interGrid.setGrid(openArray.get(4));
        //Load traffic controller settings
        tc.setCycleNumber((Integer) openArray.get(5));
        tc.setWaitNumber((Integer) openArray.get(6));
        tc.setHorState((Character) openArray.get(7));
        tc.setVertState((Character) openArray.get(8));
        tc.setPrevGreenState((Character) openArray.get(9));
        //Load cars
        interGrid.initiateGrid();
        interGrid.createLaneBoundaries();
        cars = interGrid.getCars();
        carSaveArray = (ArrayList) openArray.get(10);
        for (int i = 0; i < carSaveArray.size(); i++) {
            ArrayList arrayList = (ArrayList) carSaveArray.get(i);
            cars.add(new Car((Integer) arrayList.get(0), (Integer) arrayList.get(1), (Character) arrayList.get(2), (Character) arrayList.get(3)));
        }
        JOptionPane.showMessageDialog(null, "Simulation was successfully restored!");

    }

    public void runSimulationNumTimes(int runTimes) {
        this.runTimes = runTimes;
        this.simulationRunningCommand = true;

    }

    public void runCommand() {
        simCommand.run();
    }

    public void runGUI() {
        chatClient.go();
        mainMenu.setVisible(true);
    }
}
